//-----------------------------------------------------------------------
//
// NAME:        Session.cs
//
// PROJECT:     Battle Core Session Library
//
// COMPILER:    Microsoft Visual Studio .NET 2005
//
// DESCRIPTION: Session implementation.
//
// NOTES:       None.
//
// $History: Session.cs $
//
//-----------------------------------------------------------------------

// Namespace usage
using System;
using System.Collections.Generic;
using System.Text;
using System.Collections;
using System.Threading;
using BattleCore.Protocol;
using BattleCore.Settings;

// Namespace declaration
namespace BattleCore.Session
{
   /// <summary>
   /// Session State enumeration
   /// </summary>
   internal enum SessionState 
   { SessionStart, SessionConnect, SessionRun, SessionTimeSync, SessionDisconnected };

	/// <summary>
	/// Session packet handler delegate.  This delegate is called
   /// when a packet is received by the server. 
   /// </summary>
   public delegate void SessionPacketHandler (Byte[] pPacket);

	/// <summary>
	/// Session logger delegate.  This delegate is called
   /// when messages are generated by the session handler. 
	/// </summary>
   public delegate void SessionLogger (String message);

	/// <summary>
	/// Session login notify delegate.  This delegate is called
   /// when the bot is successfully logged in. 
	/// </summary>
   public delegate void LoginNotify ();

	/// <summary>
	/// Session disconnect notify delegate.  This delegate is called
   /// when the bot is disconnected from the server. 
	/// </summary>
   public delegate void DisconnectNotify ();

   /// <summary>
   /// Packet Handler Object 
   /// </summary>
   internal class PacketHandler
   {
      /// <summary>
      /// Message Identifier
      /// </summary>
      public Byte MessageId;

      /// <summary>
      /// Session Handler Delegate
      /// </summary>
      public SessionPacketHandler Handler;

      /// <summary>
      /// Packet Handler Constructor
      /// </summary>
      /// <param name="msgId">Message Identifier</param>
      /// <param name="handler">Packet Handler Delegate</param>
      public PacketHandler (Byte msgId, SessionPacketHandler handler)
      {
         // Initialize the member data
         MessageId = msgId;
         Handler = handler;
      }
   }

   /// <summary>
   /// Session Object.  This object handles a session with the server
   /// </summary>
   internal partial class Session
   {
      /// <summary>
      /// Network Connection to the server
      /// </summary>
      private SessionSocket m_sessionSocket = new SessionSocket ();

      /// <summary>
      /// Session Statistics used to generate the security response
      /// </summary>
      private SessionStatistics m_sessionStatistics = new SessionStatistics ();

      /// <summary>
      /// Session Settings used to configure this session
      /// </summary>
      private SessionSettings m_sessionSettings = new SessionSettings ();

      /// <summary>
      /// Server Sync request time stamp
      /// </summary>
      private TimeSpan m_serverSyncTime = TimeSpan.FromSeconds(0);

      /// <summary>
      /// Session information logging delegate
      /// </summary>
      private SessionLogger m_sessionLogger;

      private LoginNotify m_loginSuccessNotify;
      /// <summary>
      /// Login Notification delegate
      /// </summary>
      public LoginNotify LoginSuccessHandler
      {
         set { m_loginSuccessNotify = value; }
         get { return m_loginSuccessNotify; }
      }

      SecurityChecksum m_securityChecksum = new SecurityChecksum ();
      /// <summary>
      /// Get the security checksum handler
      /// </summary>
      public SecurityChecksum SecurityChecksum
      {
         get { return m_securityChecksum; }
      }

      /// <summary>
      /// Disconnect Notification delegate
      /// </summary>
      private DisconnectNotify m_disconnectNotify;
      public DisconnectNotify DisconnectHandler
      {
         set { m_disconnectNotify = value; }
         get { return m_disconnectNotify; }
      }

      /// <summary>
      /// Game Packet Handler Jump Table
      /// </summary>
      SortedList m_gamePacketHandlers = new SortedList ();

      /// <summary>
      /// Special Packet Handler Jump Table
      /// </summary>
      SortedList m_specialPacketHandlers = new SortedList ();

      /// <summary>
      /// Session Constructor
      /// </summary>
      public Session ()
      {
         // Add the delegate event handlers
         m_sessionLogger      = new SessionLogger (MessageHandler);
         m_loginSuccessNotify = new LoginNotify (HandleLoginSuccessful);
         m_disconnectNotify   = new DisconnectNotify (HandleDisconnectNotify);
      }

      /// <summary>
      /// Session message log handler
      /// </summary>
      /// <param name="message">Message String</param>
      private void MessageHandler (String message)
      { 
         // Write the message to the console
         Console.WriteLine (message); 
      }

      /// <summary>
      /// Add a session message handler to the session logger
      /// </summary>
      public SessionLogger MessageLogger { set { m_sessionLogger += value; } }

      /// <summary>
      /// Property to add a packet logger to the socket
      /// </summary>
      public PacketLogger PacketLog { set { m_sessionSocket.PacketLog = value; } }

      /// <summary>
      /// Property method to register a game packet handler
      /// </summary>
      public PacketHandler GamePacketHandler
      {
         set
         {
            int nIndex = m_gamePacketHandlers.IndexOfKey (value.MessageId);

            if (nIndex == -1)
            {
               // Add the handler to the Handler list
               m_gamePacketHandlers.Add (value.MessageId, value); ;
            }
            else
            {
               // Get the existing reference to the packet handler
               ((PacketHandler)(m_gamePacketHandlers.GetByIndex (nIndex))).Handler += value.Handler;
            }
         }
      }

      /// <summary>
      /// Property method to register a special packet handler
      /// </summary>
      public PacketHandler SpecialPacketHandler 
      { 
         set
         {
            int nIndex = m_specialPacketHandlers.IndexOfKey (value.MessageId);

            if (nIndex == -1)
            {
               // Add the handler to the Handler list
               m_specialPacketHandlers.Add (value.MessageId, value);
            }
            else
            {
               // Get the existing reference to the packet handler
               ((PacketHandler)(m_specialPacketHandlers.GetByIndex(nIndex))).Handler += value.Handler;
            }
         }
      }

      /// <summary>
      /// Register a game packet handler
      /// </summary>
      /// <param name="nMessageId">Message identifier</param>
      /// <param name="handler">Packet Handler</param>
      public void AddGamePacketHandler (Byte nMessageId, SessionPacketHandler handler)
      {
         GamePacketHandler = new PacketHandler (nMessageId, handler);
      }

      /// <summary>
      /// Register a special game packet handler
      /// </summary>
      /// <param name="nMessageId">Message identifier</param>
      /// <param name="handler">Packet Handler</param>
      public void AddSpecialPacketHandler (Byte nMessageId, SessionPacketHandler handler)
      {
         SpecialPacketHandler = new PacketHandler (nMessageId, handler);
      }

      /// <summary>
      /// Session Thread Control Object
      /// </summary>
      private class SessionThreadInfo
      {
         public ManualResetEvent m_pKill = new ManualResetEvent (false);  // Thread kill event
         public ManualResetEvent m_pDead = new ManualResetEvent (true);  // Thread dead event
      };

      /// <summary>
      /// Session Thread 
      /// </summary>
      private Thread m_pSessionThread;  

      /// <summary>
      /// Session thread control 
      /// </summary>
      SessionThreadInfo m_sessionThreadInfo = new SessionThreadInfo ();

      /// <summary>
      /// Start a session with the server
      /// </summary>
      /// <param name="sessionSettings">Session Settings</param>
      public void Start (SessionSettings sessionSettings)
      {
         // Save the session settings
         m_sessionSettings = sessionSettings;

         // Reset the session statistics
         m_sessionStatistics.Reset ();

         // Create the session thread
         m_pSessionThread = new Thread (new ThreadStart (SessionThread));

         // Start the session thread
         m_pSessionThread.Start ();
      }

      /// <summary>
      /// Close a session with the server
      /// </summary>
      public void Close ()
      {
         // Stop the session thread
         m_sessionThreadInfo.m_pKill.Set ();
         m_sessionThreadInfo.m_pDead.WaitOne (500, false);
      }

      /// <summary>
      /// Session processing thread
      /// </summary>
      private void SessionThread ()
      {
         SessionState sessionState = SessionState.SessionStart;

         // Create the timestamp values
         TimeSpan currentTime     = TimeSpan.FromMilliseconds(Environment.TickCount);
         TimeSpan activityTimeout = currentTime + TimeSpan.FromSeconds(10);

         // Read the current timeout settings
         TimeSpan configuredTimeout = TimeSpan.FromSeconds(m_sessionSettings.ActivityTimeout);

         // Reset the thread dead event
         m_sessionThreadInfo.m_pDead.Reset ();

         // Loop until a kill request is received
         while (!m_sessionThreadInfo.m_pKill.WaitOne (1, false))
         {
            try
            {
               // Get the current timestamp
               currentTime = TimeSpan.FromMilliseconds (Environment.TickCount);

               // Process the current session state
               switch (sessionState)
               {
               case SessionState.SessionStart:
                  // Create a new Session socket
                  m_sessionSocket = new SessionSocket ();
                  sessionState = SessionState.SessionConnect;
                  break;

               case SessionState.SessionConnect:
                  // Create the server connection
                  m_sessionSocket.Connect (m_sessionSettings.ServerAddress, 
                                           m_sessionSettings.ServerPort);

                  // Check if the connection was established
                  if (m_sessionSocket.Active)
                  {
                     // Reset the session statistics
                     m_sessionStatistics.Reset ();

                     // Send the encryption request        
                     SendEncryptionRequest ();

                     // Recalculate the activity timeout value
                     activityTimeout = currentTime + configuredTimeout;

                     // Connect to the server
                     sessionState = SessionState.SessionRun;
                  }
                  break;

               case SessionState.SessionRun:
                  // Handle the server messages
                  Byte[] pServerData = m_sessionSocket.ReceivePacket ();

                  if ((pServerData != null) && (pServerData.Length > 0))
                  {
                     // Process the server message
                     PacketReceiveHandler (pServerData);

                     // Recalculate the activity timeout value
                     activityTimeout = currentTime + configuredTimeout;
                  }
                  else if (currentTime > activityTimeout)
                  {
                     // Set the state to disconnected due to inactivity
                     sessionState = SessionState.SessionDisconnected;
                  }
                  break;

               case SessionState.SessionTimeSync:
                  // Synchronize the client and server clocks
                  SendSynchronizeRequest ();

                  // Return the the session Run state
                  sessionState = SessionState.SessionRun;
                  break;

               case SessionState.SessionDisconnected:
                  // Send a disconnect request message
                  SendDisconnectRequest ();

                  // Set the server sync time
                  m_serverSyncTime = TimeSpan.FromSeconds (0);

                  // Notify that the session is disconnected
                  m_disconnectNotify ();

                  // Return the the session connect state
                  sessionState = SessionState.SessionStart;
                  break;
               }

               // Check if it is time to timesync with the server
               if ((m_serverSyncTime.Seconds != 0) && (currentTime > m_serverSyncTime))
               {
                  // Synchronize time with the server
                  sessionState = SessionState.SessionTimeSync;
               }
            }
            catch (Exception e)
            {
               // Write the exception to the console
               Console.WriteLine (e);

               // Set the disconnected state
               sessionState = SessionState.SessionDisconnected;
            }
         }
         
         // Send the disconnect request
         SendDisconnectRequest ();

         // Notify that the session is disconnected
         m_disconnectNotify ();

         // Set the session thread dead event
         m_sessionThreadInfo.m_pDead.Set ();
      }
   }
}
